package com.mcx.modules.socket;

import com.mcx.modules.PlayerEntity;
import com.mcx.modules.PlayerMgr;
import com.mcx.modules.common.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.jni.Time;

import java.io.*;
import java.net.Socket;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Timer;

@Slf4j
@Data
public class ClientSocket implements Runnable {
    //所属角色
    private PlayerEntity m_Role;
    //客户端Socket
    private Socket m_Socket;
    //接收数据的线程
    private Thread m_ReceiveThread;
    //接收消息所需变量
    private byte[] m_ReceiveBuffer = new byte[2048];
    private MMO_DataStream dataStream = new MMO_DataStream();

    //发送消息所需变量
    //发送消息队列
    private Queue<byte[]> m_SendQueue = new LinkedList<byte[]>();
    //检查队列的委托
//    private Action m_CheckSendQuene;
    //压缩数组的长度界限
    private int m_CompressLen = 200;

    public ClientSocket(Socket socket, PlayerEntity role) throws IOException {
        m_Socket = socket;
        m_Role = role;
        m_Role.Client_Socket = this;

//        m_CheckSendQuene = OnCheckSendQueueCallBack;
        dataStream.inputStream = new DataInputStream(m_Socket.getInputStream());
        dataStream.outputStream = new DataOutputStream(m_Socket.getOutputStream());

        //启动线程 进行接收数据
        m_ReceiveThread = new Thread(this::run);
        m_ReceiveThread.start();
    }

    @Override
    public void run() {
        MMO_DataStream ms = new MMO_DataStream();
        while (true) {
            try {
                int available = dataStream.inputStream.available();
                if ( available > 0) {
                    //已经接收到数据
                    //把接收到数据 写入缓冲数据流的尾部
                    //把指定长度的字节 写入数据流

                    //如果缓存数据流的长度>2 说明至少有个不完整的包过来了
                    //为什么这里是2 因为我们客户端封装数据包 用的ushort 长度就是2
                    if (available > 4)
                    {
                        //进行循环 拆分数据包
                        while (true)
                        {
                            //把数据流指针位置放在0处

                            //currMsgLen = 包体的长度
                            int currMsgLen = dataStream.ReadShort();
//                        log.info("currMsgLen:"+currMsgLen);

                            //currFullMsgLen 总包的长度=包头长度+包体长度
                            int currFullMsgLen = 2 + currMsgLen;

                            //如果数据流的长度>=整包的长度 说明至少收到了一个完整包
                            if (available >= currFullMsgLen)
                            {
                                //至少收到一个完整包

                                //定义包体的byte[]数组
                                byte[] buffer = new byte[currMsgLen];

                                //把数据流指针放到2的位置 也就是包体的位置

                                //把包体读到byte[]数组
                                dataStream.inputStream.read(buffer, 0, currMsgLen);

                                //===================================================
                                // 异或之后的数组
                                byte[] bufferNew = new byte[buffer.length - 3];

                                Boolean isCompress = false;
                                short crc = 0;

                                ms.inputStream = new DataInputStream(new ByteArrayInputStream(buffer));
                                //                              压缩标识
                                isCompress = ms.ReadBool();
                                crc = ms.ReadShort();
                                ms.inputStream.read(bufferNew, 0, bufferNew.length);

                                // 先crc
                                int newCrc = CRC.crc16_modbus(bufferNew, 0, bufferNew.length);

                                if ((short) newCrc==crc)
                                {
                                     // 异或 得到原始数据
                                     bufferNew = SecurityUtil.Xor(bufferNew);

                                     if (isCompress)
                                     {
                                         bufferNew = ZLibUtils.decompress(bufferNew);
                                     }

                                    //协议编号
                                    short protoCode = 0;
                                    byte[] protoContent = new byte[bufferNew.length - 2];

                                    ms.inputStream = new DataInputStream(new ByteArrayInputStream(bufferNew));
                                    protoCode = ms.ReadShort();
                                    ms.inputStream.read(protoContent, 0, protoContent.length);

                                    EventDispatcher.getInstance().Dispatch(protoCode, m_Role, protoContent);
                                }
                                else
                                {
                                    break;
                                }

                                //==============处理剩余字节数组===================

                                //剩余字节长度
                                int remainLen = available - currFullMsgLen;
                                if (remainLen > 0)
                                {
                                    //把指针放在第一个包的尾部

//                                //定义剩余字节数组
//                                byte[] remainBuffer = new byte[remainLen];
//
//                                //把数据流读到剩余字节数组
//                                inputStream.read(remainBuffer, currFullMsgLen, remainLen);
//
//                                //清空数据流
//                                outputStream.flush();
//
//                                //把剩余字节数组重新写入数据流
//                                outputStream.write(remainBuffer, 0, remainBuffer.length);

//                                remainBuffer = null;
                                    break;
                                }
                                else
                                {
                                    //没有剩余字节

                                    //清空数据流
//                                outputStream.flush();

                                    break;
                                }
                            }
                            else
                            {
                                //还没有收到完整包
                                break;
                            }
                        }
                    }
                    ReceiveMsg();
                }
            } catch (IOException e) {
                e.printStackTrace();
                //客户端断开连接
                log.info(String.format("客户端%d断开连接 原因%d", m_Socket.getRemoteSocketAddress().toString(),e.toString()));
                PlayerMgr.getInstance().getAllRole().remove(m_Role);
            }
        }
//        log.info(String.format("客户端%d断开连接", m_Socket.getRemoteSocketAddress().toString()));
//        PlayerMgr.getInstance().getAllRole().remove(m_Role);
    }

    private void ReceiveMsg(){

    }

    //====================================================

    /**
     * 检查队列的委托回调
     */
    private void OnCheckSendQueueCallBack() throws IOException {
//        lock (m_SendQueue)
        {
            //如果队列中有数据包 则发送数据包
            if (m_SendQueue.size() > 0)
            {
                Send(m_SendQueue.poll());
            }
        }
    }

    /**
     * 封装数据包
     * @param data
     * @return
     */
    private byte[] MakeData(byte[] data) {
        Boolean isCompress = data.length > m_CompressLen;
        if (isCompress)
        {
            data = ZLibUtils.compress(data);
        }
        data = SecurityUtil.Xor(data);
        int crc = CRC.crc16_modbus(data, 0, data.length);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        MMO_DataStream ms = new MMO_DataStream();
        try (DataOutputStream outputStream = new DataOutputStream(out)) {
            ms.outputStream = outputStream;
            ms.WriteShort((short) (data.length + 3));
            ms.WriteBool(isCompress);
            ms.WriteShort((short) crc);
            ms.outputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return out.toByteArray();
    }

    /**
     * 发送消息
     * @param buffer
     */
    public void SendMsg(byte[] buffer) throws IOException {
        //得到封装后的数据包
        byte[] sendBuffer = MakeData(buffer);
//        log.info(Arrays.toString(sendBuffer));

//        lock (m_SendQueue)
        {
            //把数据包加入队列
            m_SendQueue.offer(sendBuffer);

            //启动委托（执行委托）
            OnCheckSendQueueCallBack();
        }
    }

    /**
     * 真正发送数据包到服务器
     * @param buffer
     */
    private void Send(byte[] buffer) throws IOException {
        dataStream.outputStream.write(buffer, 0, buffer.length);
    }

    /**
     * 发送数据包的回调
     */
    private void SendCallBack() throws IOException {
        //继续检查队列
        OnCheckSendQueueCallBack();
    }
}
